// -*- Mode: C; c-basic-offset: 8; -*-
//
// Copyright (c) 2012 Andrew Tridgell, All Rights Reserved
// Copyright (c) 2011 Michael Smith, All Rights Reserved
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//  o Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  o Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in
//    the documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
//

///
/// @file	tdm.c
///
/// time division multiplexing code
///

#include <stdarg.h>
#include "radio.h"
#include "tdm.h"
#include "timer.h"
#include "packet.h"
#include "golay.h"
#include "freq_hopping.h"
#include "crc.h"
#include "serial.h"

#ifdef INCLUDE_AES
#include "AES/aes.h"
#endif // INCLUDE_AES

#define USE_TICK_YIELD 1

/// the state of the tdm system
enum tdm_state
{
    TDM_TRANSMIT = 0,
    TDM_SILENCE1 = 1,
    TDM_RECEIVE = 2,
    TDM_SILENCE2 = 3
};
static enum tdm_state tdm_state;

/// a packet buffer for the TDM code
uint8_t pbuf[MAX_PACKET_LENGTH];

/// how many 16usec ticks are remaining in the current state
static uint16_t tdm_state_remaining;

/// This is enough to hold at least 3 packets and is based
/// on the configured air data rate.
static uint16_t tx_window_width;

/// the maximum data packet size we can fit
static uint8_t max_data_packet_length;

/// the silence period between transmit windows
/// This is calculated as the number of ticks it would take to transmit
/// two zero length packets
static uint16_t silence_period;

/// whether we can transmit in the other radios transmit window
/// due to the other radio yielding to us
static unsigned char bonus_transmit;

/// whether we have yielded our window to the other radio
static unsigned char transmit_yield;

// activity indication
// when the 16 bit timer2_tick() value wraps we check if we have received a
// packet since the last wrap (ie. every second)
// If we have the green radio LED is held on.
// Otherwise it blinks every 1 seconds. The received_packet flag
// is set for any received packet, whether it contains user data or
// not.
static unsigned char blink_state;
static unsigned char received_packet;

/// the latency in 16usec timer2 ticks for sending a zero length packet
static uint16_t packet_latency;

/// the time in 16usec ticks for sending one byte
static uint16_t ticks_per_byte;

/// number of 16usec ticks to wait for a preamble to turn into a packet
/// This is set when we get a preamble interrupt, and causes us to delay
/// sending for a maximum packet latency. This is used to make it more likely
/// that two radios that happen to be exactly in sync in their sends
/// will eventually get a packet through and get their transmit windows
/// sorted out
uint16_t transmit_wait;

/// the long term duty cycle we are aiming for
uint8_t duty_cycle;

/// the average duty cycle we have been transmitting
static float average_duty_cycle;

/// duty cycle offset due to temperature
uint8_t duty_cycle_offset;

/// set to true if we need to wait for our duty cycle average to drop
static bool duty_cycle_wait;

/// how many ticks we have transmitted for in this TDM round
static uint16_t transmitted_ticks;

/// the LDB (listen before talk) RSSI threshold
uint8_t lbt_rssi;

/// how long we have listened for for LBT
static uint16_t lbt_listen_time;

/// how long we have to listen for before LBT is OK
static uint16_t lbt_min_time;

/// random addition to LBT listen time (see European regs)
static uint16_t lbt_rand;

/// test data to display in the main loop. Updated when the tick
/// counter wraps, zeroed when display has happened
uint8_t test_display;

/// set when we should send a statistics packet on the next round
static unsigned char send_statistics;

/// set when we should send a MAVLink report pkt
extern uint8_t seen_mavlink;

struct tdm_trailer
{
    uint16_t window : 13;
    uint16_t command : 1;
    uint16_t bonus : 1; // 对方让我增加窗口补偿时间！！！
    uint16_t resend : 1;
#ifdef INCLUDE_AES
    uint16_t crc;
#endif
};
struct tdm_trailer trailer;

/// buffer to hold a remote AT command before sending
static bool send_at_command;
static char remote_at_cmd[AT_CMD_MAXLEN + 1];

#define PACKET_OVERHEAD (sizeof(trailer) + 16)

/// display RSSI output
///
void tdm_show_rssi(void)
{
    printf("L/R RSSI: %u/%u  L/R noise: %u/%u pkts: %u ",
           (unsigned)statistics.average_rssi,
           (unsigned)remote_statistics.average_rssi,
           (unsigned)statistics.average_noise,
           (unsigned)remote_statistics.average_noise,
           (unsigned)statistics.receive_count);
#ifdef INCLUDE_AES
    printf(" txe=%u rxe=%u stx=%u srx=%u ecc=%u/%u crce=%u temp=%d dco=%u\n",
#else
    printf(" txe=%u rxe=%u stx=%u srx=%u ecc=%u/%u temp=%d dco=%u\n",
#endif
           (unsigned)errors.tx_errors,
           (unsigned)errors.rx_errors,
           (unsigned)errors.serial_tx_overflow,
           (unsigned)errors.serial_rx_overflow,
           (unsigned)errors.corrected_errors,
           (unsigned)errors.corrected_packets,
#ifdef INCLUDE_AES
           (unsigned)errors.crc_errors,
#endif
           (int)radio_temperature(),
           (unsigned)duty_cycle_offset);
    statistics.receive_count = 0;
}

/// display test output
///
static void
display_test_output(void)
{
    if (test_display & AT_TEST_RSSI)
    {
        tdm_show_rssi();
    }
}

/// estimate the flight time for a packet given the payload size
///
/// @param packet_len		payload length in bytes
///
/// @return			flight time in 16usec ticks
static uint16_t flight_time_estimate(uint8_t packet_len)
{
    return packet_latency + (packet_len * ticks_per_byte);
}

/// synchronise tx windows
///
/// we receive a 16 bit value with each packet which indicates how many
/// more 16usec ticks the sender has in their transmit window. The
/// sender has already adjusted the value for the flight time
///
/// The job of this function is to adjust our own transmit window to
/// match the other radio and thus bring the two radios into sync
///
static void
sync_tx_windows(uint8_t packet_length)
{
    enum tdm_state old_state = tdm_state;
    uint16_t old_remaining = tdm_state_remaining;

    if (trailer.bonus) //
    {
        // the other radio is using our transmit window
        // via yielded ticks
        if (old_state == TDM_SILENCE1)
        {
            // This can be caused by a packet
            // taking longer than expected to arrive.
            // don't change back to transmit state or we
            // will cause an extra frequency change which
            // will get us out of sequence
            tdm_state_remaining = silence_period;
        }
        else if (old_state == TDM_RECEIVE || old_state == TDM_SILENCE2)
        {
            // this is quite strange. We received a packet
            // so we must have been on the right
            // frequency. Best bet is to set us at the end
            // of their silence period
            tdm_state = TDM_SILENCE2;
            tdm_state_remaining = 1;
        }
        else
        {
            tdm_state = TDM_TRANSMIT;
            tdm_state_remaining = trailer.window;
        }
    }
    else
    {
        // we are in the other radios transmit window, our
        // receive window
        tdm_state = TDM_RECEIVE;
        tdm_state_remaining = trailer.window;
    }

    // if the other end has sent a zero length packet and we are
    // in their transmit window then they are yielding some ticks to us.
    bonus_transmit = (tdm_state == TDM_RECEIVE && packet_length == 0);

    // if we are not in transmit state then we can't be yielded
    if (tdm_state != TDM_TRANSMIT)
    {
        transmit_yield = 0;
    }

    if (at_testmode & AT_TEST_TDM)
    {
        int16_t delta;
        delta = old_remaining - tdm_state_remaining;
        if (old_state != tdm_state ||
            delta > (int16_t)packet_latency / 2 ||
            delta < -(int16_t)packet_latency / 2)
        {
            printf("TDM: %u/%u len=%u ",
                   (unsigned)old_state,
                   (unsigned)tdm_state,
                   (unsigned)packet_length);
            printf(" delta: %d\n", (int)delta);
        }
    }
}

/// update the TDM state machine
///
static void
tdm_state_update(uint16_t tdelta)
{
    // update the amount of time we are waiting for a preamble
    // to turn into a real packet
    if (tdelta > transmit_wait)
    {
        transmit_wait = 0;
    }
    else
    {
        transmit_wait -= tdelta;
    }

    // have we passed the next transition point?
    while (tdelta >= tdm_state_remaining)
    {
        // advance the tdm state machine
        tdm_state = (tdm_state + 1) % 4; // 发送==> 静默 ==> 接收 ==> 静默 ==>发送 工作循环

        // work out the time remaining in this state
        tdelta -= tdm_state_remaining;

        if (tdm_state == TDM_TRANSMIT || tdm_state == TDM_RECEIVE)
        {
            tdm_state_remaining = tx_window_width;
        }
        else
        {
            tdm_state_remaining = silence_period;
        }

        // change frequency at the start and end of our transmit window
        // this maximises the chance we will be on the right frequency
        // to match the other radio
        if (tdm_state == TDM_TRANSMIT || tdm_state == TDM_SILENCE1)
        {
            fhop_window_change();
            radio_receiver_on();

            if (num_fh_channels > 1)
            {
                // reset the LBT listen time
                lbt_listen_time = 0;
                lbt_rand = 0;
            }
        }

        if (tdm_state == TDM_TRANSMIT && (duty_cycle - duty_cycle_offset) != 100)
        {
            // update duty cycle averages
            average_duty_cycle = (0.95 * average_duty_cycle) + (0.05 * (100.0 * transmitted_ticks) / (2 * (silence_period + tx_window_width)));
            transmitted_ticks = 0;
            duty_cycle_wait = (average_duty_cycle >= (duty_cycle - duty_cycle_offset));
        }

        // we lose the bonus on all state changes
        bonus_transmit = 0;

        // reset yield flag on all state changes
        transmit_yield = 0;

        // no longer waiting for a packet
        transmit_wait = 0;
    }

    tdm_state_remaining -= tdelta;
}

/// change tdm phase
///
void tdm_change_phase(void)
{
    tdm_state = (tdm_state + 2) % 4;
}

/// called to check temperature
/// 这里是温度控制，如果温度高于20，那么数传将会主动降低发送占空时间 duty cycle 就是传输占空时间，这个时间是动态的
static void temperature_update(void)
{
    register int16_t diff;
    if (radio_get_transmit_power() <= 20)
    {
        duty_cycle_offset = 0;
        return;
    }

    diff = radio_temperature() - MAX_PA_TEMPERATURE;
    if (diff <= 0 && duty_cycle_offset > 0)
    {
        // under temperature
        duty_cycle_offset -= 1;
    }
    else if (diff > 10)
    {
        // getting hot!
        duty_cycle_offset += 10;
    }
    else if (diff > 5)
    {
        // well over temperature
        duty_cycle_offset += 5;
    }
    else if (diff > 0)
    {
        // slightly over temperature
        duty_cycle_offset += 1;
    }
    // limit to minimum of 20% duty cycle to ensure link stays up OK
    if ((duty_cycle - duty_cycle_offset) < 20)
    {
        duty_cycle_offset = duty_cycle - 20;
    }
}

/// blink the radio LED if we have not received any packets
///
static void
link_update(void)
{
    static uint8_t unlock_count = 10, temperature_count;
    if (received_packet)
    {
        unlock_count = 0;
        received_packet = false;
#ifdef TDM_SYNC_LOGIC
        TDM_SYNC_PIN = true;
#endif // TDM_SYNC_LOGIC
    }
    else
    {
        unlock_count++;
    }

    if (unlock_count < 2)
    {
#error "need LED_RADIO = LED_ON;"
    }
    else
    {
#ifdef TDM_SYNC_LOGIC
        TDM_SYNC_PIN = false;
#endif // TDM_SYNC_LOGIC

#error LED_RADIO = blink_state;

        blink_state = !blink_state;
    }

    if (unlock_count > 40)
    {
        // if we have been unlocked for 20 seconds
        // then start frequency scanning again

        unlock_count = 5;
        // randomise the next transmit window using some
        // entropy from the radio if we have waited
        // for a full set of hops with this time base
        if (timer_entropy() & 1)
        {
            register uint16_t old_remaining = tdm_state_remaining;
            if (tdm_state_remaining > silence_period)
            {
                tdm_state_remaining -= packet_latency;
            }
            else
            {
                tdm_state_remaining = 1;
            }
            if (at_testmode & AT_TEST_TDM)
            {
                printf("TDM: change timing %u/%u\n",
                       (unsigned)old_remaining,
                       (unsigned)tdm_state_remaining);
            }
        }

        if (at_testmode & AT_TEST_TDM)
        {
            printf("TDM: scanning\n");
        }
        fhop_set_locked(false);
    }

    if (unlock_count != 0)
    {
        statistics.average_rssi = (radio_last_rssi() + 3 * (uint16_t)statistics.average_rssi) / 4;

        // reset statistics when unlocked
        statistics.receive_count = 0;
    }

    if (unlock_count > 5)
    {
        memset(&remote_statistics, 0, sizeof(remote_statistics));
    }

    test_display = at_testmode;
    send_statistics = 1;

    temperature_count++;
    if (temperature_count == 4)
    {
        // check every 2 seconds
        temperature_update();
        temperature_count = 0;
    }
}

// dispatch an AT command to the remote system
void tdm_remote_at(void)
{
    memcpy(remote_at_cmd, at_cmd, strlen(at_cmd) + 1);
    send_at_command = true;
}

// handle an incoming at command from the remote radio
//
// Return true if returning a pbuf that needs to be sent to output
//        false if data is going out to the other modem
static bool
handle_at_command(uint8_t len)
{
    if (len < 2 || len > AT_CMD_MAXLEN ||
        pbuf[0] != (uint8_t)'R' ||
        pbuf[1] != (uint8_t)'T')
    {
        return true;
    }

    // setup the command in the at_cmd buffer
    memcpy(at_cmd, pbuf, len);
    at_cmd[len] = 0;
    at_cmd[0] = 'A'; // replace 'R'
    at_cmd_len = len;
    at_cmd_ready = true;

    // run the AT command, capturing any output to the packet
    // buffer
    // this reply buffer will be sent at the next opportunity
    printf_start_capture(pbuf, sizeof(pbuf));
    at_command();
    len = printf_end_capture();
    if (len > 0)
    {
        packet_inject(pbuf, len);
    }
    return false;
}

// a stack carary to detect a stack overflow
uint8_t _canary;

/// main loop for time division multiplexing transparent serial
///
void tdm_serial_loop(void)
{
#ifdef RADIO_SPLAT_TESTING_MODE
    for (;;)
    {
        radio_set_channel(0);
        radio_transmit(MAX_PACKET_LENGTH, pbuf, 0);
        // radio_receiver_on();
    }
#else
    uint8_t len;
    uint16_t tnow, tdelta;
    uint8_t max_xmit;
#ifdef INCLUDE_AES
    uint16_t crc;
#endif // INCLUDE_AES
    uint16_t last_t = timer2_tick();
    uint16_t last_link_update = last_t;

    _canary = 42;

    for (;;)
    {
        if (_canary != 42)
        {
            panic("stack blown\n");
        }

        if (pdata_canary != 0x41)
        {
            panic("pdata canary changed\n");
        }

        // give the AT command processor a chance to handle a command
        /* 这个AT在串口中断里边获取数据，实际上是 串口缓存和AT缓存各一份 */
        at_command();

        // display test data if needed
        /*** 这个相当于debugout 其状态在 link_update() 刷新 源自于at_testmode*/
        if (test_display)
        {
            display_test_output();
            test_display = 0;
        }

        /** 这里数传每隔一段时间 maybe？ 通过串口上报mavlink数据给飞控（实际上） */
        if (seen_mavlink && feature_mavlink_framing && !at_mode_active)
        {
            if (MAVLink_report())
            {
                seen_mavlink = 0;
            }
        }

        // set right receive channel
        /** 根据 hopping 状态 获取当前channel 并设置 这里是个关键代码 需要驱动******************************************************************************************************************************************** */
        radio_set_channel(fhop_receive_channel());

        // get the time before we check for a packet coming in
        /*****这里更新当前时间 感觉像是 时间基础************************************************ */
        tnow = timer2_tick();

        // see if we have received a packet
        /** 这里检查是不是有无线收到数据 这个需要驱动****************************************************************** */
        if (radio_receive_packet(&len, pbuf))
        {

            // update the activity indication
            received_packet = true;
            fhop_set_locked(true); /**这里应该是做原子操作，防止意外跳频    不！！！这里是捕获到了，同步收发信道 用来做同步的 ***************** */

            // update filtered RSSI value and packet stats 这里获取自己的RSSI，他还整了个滤波器 6666666 ，平均RSSI值
            statistics.average_rssi = (radio_last_rssi() + 7 * (uint16_t)statistics.average_rssi) / 8;
            statistics.receive_count++;

            // we're not waiting for a preamble
            // any more
            transmit_wait = 0; // 这个我暂时没看懂 把发送等待设置成 0 实际上接收数据完成就标志着射频空闲 允许发送数据

            if (len < 2)
            {
                // not a valid packet. We always send 数据包不够长，他的控制块都要2个字节 控制块是 struct tdm_trailer trailer
                // two control bytes at the end of every packet
                continue;
            }

            // extract control bytes from end of packet 这里把控制块拿出来 他的控制块是跟在数据后边的
            memcpy(&trailer, &pbuf[len - sizeof(trailer)], sizeof(trailer));
            len -= sizeof(trailer);

            if (trailer.window == 0 && len != 0) // 由于window==0 ，这是一个控制包，追踪了一下，他丫居然是动态的不晓得是不是可以动态控制跳频时间
            {
                // its a control packet
                if (len == sizeof(struct statistics))
                {
                    memcpy(&remote_statistics, pbuf, len);
                }

                // don't count control packets in the stats
                statistics.receive_count--; // 这个位置是说这个包不上算，他不是数据
            }
            else if (trailer.window != 0) // 这个是数据包
            {
                // sync our transmit windows based on
                // received header
                sync_tx_windows(len); // 同步发送等待窗口，猜测=====> 他在每次传输的时候都要加个窗口，当一帧数据过长的时候，他需要分包传输，每包里边窗口时间递减，直到数据发完？
                last_t = tnow;

                // Send data to console (serial buffers) if following conditions met
                // If is a command and data is destined to THIS modem
                // OR
                // data is present, not a command, not a dup and not in AT Mode
                //
                // Question: Are we happy to blink Activity lights for RT command results?
                if ((trailer.command == 1 && handle_at_command(len)) || // 这行判断是不是远程AT命令
                    (len != 0 && trailer.command == 0 &&                // 这行判断是数据包
                     !packet_is_duplicate(len, pbuf, trailer.resend) && // 且不是重传包
                     !at_mode_active))                                  // AT也没开
                {
                    // its user data - send it out
                    // the serial port
#ifdef INCLUDE_AES
                    crc = crc16(len, pbuf);
                    // Only of CRC's agree do we process the pbuf
                    // (We can't decrypt a packet that is corrupt)
                    if (crc == trailer.crc)
                    {
                        LED_ACTIVITY = LED_ON;
                        serial_decrypt_buf(pbuf, len);
                        LED_ACTIVITY = LED_OFF;
                    }
                    else
                    {
                        if (errors.crc_errors != 0xFFFF)
                        {
                            errors.crc_errors++;
                        }
                    }
#else // INCLUDE_AES
#error LED_ACTIVITY = LED_ON;
                    serial_write_buf(pbuf, len); // 妥妥的数据，发到串口去，我可以把这个搞成消息队列。丫推到队列里边去
#error LED_ACTIVITY = LED_OFF;
#endif // INCLUDE_AES
                }
            }
            continue; // 只要收到数据包了，咋就不用后边的操作了，后边纯粹是莫得数据包了，才需要定时跳频，收到数据后，除非接收窗口时间到，不然不发数据
        }

        // see how many 16usec ticks have passed and update
        // the tdm state machine. We re-fetch tnow as a bad
        // packet could have cost us a lot of time.
        tnow = timer2_tick();
        tdelta = tnow - last_t;   // 这里在更新接收窗口
        tdm_state_update(tdelta); // 这个更新状态，当前频率窗口时间会减去tdelta，如果减到没有且窗口时间小于0，就会跳频，而且会根据频率窗口时间决定跳多少个频
        last_t = tnow;

        // update link status every 0.5s 这个是心跳包，他每0.5秒发一次
        if (tnow - last_link_update > 32768)
        {
            link_update();
            last_link_update = tnow;
        }
        // 发送前监听，这里是为了进行通讯避让，丫根据接收RSSI进行判定，当前信道是不是已经有人在发数据了
        // lbt_rssi是监听阈值，如果RSSI小于这个值，就会进入监听状态,他在上电、AT配置。远程at配置的时候更新
        if (lbt_rssi != 0)
        {
            // implement listen before talk
            if (radio_current_rssi() < lbt_rssi)
            {
                lbt_listen_time += tdelta;
            }
            else
            {
                lbt_listen_time = 0;
                if (lbt_rand == 0)
                {
                    lbt_rand = ((uint16_t)r_rand()) % lbt_min_time;
                }
            }
            if (lbt_listen_time < lbt_min_time + lbt_rand) // 增加个随机监听时间
            {
                // we need to listen some more
                continue;
            }
        }

        // we are allowed to transmit in our transmit window
        // or in the other radios transmit window if we have
        // bonus ticks
#if USE_TICK_YIELD
        if (tdm_state != TDM_TRANSMIT &&
            !(bonus_transmit && tdm_state == TDM_RECEIVE)) // 这里应该是补偿窗口时间，bonus_transmit
        {
            // we cannot transmit now
            continue;
        }
#else
        if (tdm_state != TDM_TRANSMIT)
        {
            continue;
        }
#endif

        if (transmit_yield != 0) // 传输挂起
        {
            // we've give up our window
            continue;
        }

        if (transmit_wait != 0) // 传输等待
        {
            // we're waiting for a preamble to turn into a packet
            continue;
        }

        if (!received_packet &&
                radio_preamble_detected() ||
            radio_receive_in_progress()) // 如果有数据包，或者正在接收数据包，暂时不发数据
        {
            // a preamble has been detected. Don't
            // transmit for a while
            transmit_wait = packet_latency;
            continue;
        }

        // sample the background noise when it is out turn to
        // transmit, but we are not transmitting,
        // averaged over around 4 samples
        statistics.average_noise = (radio_current_rssi() + 3 * (uint16_t)statistics.average_noise) / 4; // 环境噪音，原来飞控上的数据是这里来的

        if (duty_cycle_wait)
        {
            // we're waiting for our duty cycle to drop // 这个没看懂
            continue;
        }

        // how many bytes could we transmit in the time we
        // have left?
        if (tdm_state_remaining < packet_latency) // 当前频率窗口不够时间发送数据了，继续等待
        {
            // none ....
            continue;
        }
        max_xmit = (tdm_state_remaining - packet_latency) / ticks_per_byte;
        if (max_xmit < PACKET_OVERHEAD)
        {
            // can't fit the trailer in with a byte to spare 这里在计算当前窗口是不是没办法加控制块了
            continue;
        }
        // max_xmit -= PACKET_OVERHEAD;
        max_xmit -= sizeof(trailer) + 1;

#ifdef INCLUDE_AES
        if (aes_get_encryption_level() > 0)
        {
            if (max_xmit < 16)
            {
                // With AES, the cipher is up to 16 bytes larger than the text
                // we are encrypting. So we make sure we have sufficient space
                // i.e. min size of any cipher text is 16 bytes
                continue;
            }
            max_xmit -= 16;
        }
#endif // INCLUDE_AES

        if (max_xmit > max_data_packet_length) // 限制传输包长度
        {
            max_xmit = max_data_packet_length;
        }

#if PIN_MAX > 0
        // Check to see if any pins have changed state
        pins_user_check();
#endif

        // ask the packet system for the next packet to send 发送远程AT命令
        if (send_at_command &&
            max_xmit >= strlen(remote_at_cmd))
        {
            // send a remote AT command
            len = strlen(remote_at_cmd);
            memcpy(pbuf, remote_at_cmd, len);
            trailer.command = 1;
            send_at_command = false;
        }
        else // 或者传数据
        {
            // get a packet from the serial port
            len = packet_get_next(max_xmit, pbuf); // 打包数据包

            if (len > 0)
            {
                trailer.command = packet_is_injected(); // 这里看是不是命令包 packet_get_next 里边有答案
            }
            else
            {
                trailer.command = 0;
            }
#ifdef INCLUDE_AES
            trailer.crc = crc16(len, pbuf);
#endif
        }

        if (len > max_data_packet_length)
        {
            panic("oversized tdm packet");
        }

        trailer.bonus = (tdm_state == TDM_RECEIVE);
        trailer.resend = packet_is_resend();

        if (tdm_state == TDM_TRANSMIT &&
            len == 0 &&
            send_statistics &&
            max_xmit >= sizeof(statistics))
        {
            // send a statistics packet
            send_statistics = 0;
            memcpy(pbuf, &statistics, sizeof(statistics));
            len = sizeof(statistics);

            // mark a stats packet with a zero window
            trailer.window = 0;
            trailer.resend = 0;
        }
        else
        {
            // calculate the control word as the number of
            // 16usec ticks that will be left in this
            // tdm state after this packet is transmitted

#ifdef INCLUDE_AES
            if (aes_get_encryption_level() > 0)
            {
                // Calculation here gives length of cipher text (= same length of padded block)
                trailer.window = (uint16_t)(tdm_state_remaining - flight_time_estimate(16 * (1 + (len + sizeof(trailer) >> 4))));
            }
            else
            {
                trailer.window = (uint16_t)(tdm_state_remaining - flight_time_estimate(len + sizeof(trailer)));
            }
#else  // INCLUDE_AES
            trailer.window = (uint16_t)(tdm_state_remaining - flight_time_estimate(len + sizeof(trailer)));
#endif // INCLUDE_AES
        }

        // set right transmit channel 他妈的 这里才进入发射状态
        radio_set_channel(fhop_transmit_channel());

        memcpy(&pbuf[len], &trailer, sizeof(trailer));

        if (len != 0 && trailer.window != 0)
        {
            // show the user that we're sending real data
#error LED_ACTIVITY = LED_ON;
        }

        if (len == 0)
        {
            // sending a zero byte packet gives up
            // our window, but doesn't change the
            // start of the next window
            transmit_yield = 1;
        }

        // after sending a packet leave a bit of time before
        // sending the next one. The receivers don't cope well
        // with back to back packets
        transmit_wait = packet_latency;

        // if we're implementing a duty cycle, add the
        // transmit time to the number of ticks we've been transmitting
        if ((duty_cycle - duty_cycle_offset) != 100)
        {
            transmitted_ticks += flight_time_estimate(len + sizeof(trailer));
        }

        // start transmitting the packet 正经发送数据
        if (!radio_transmit(len + sizeof(trailer), pbuf, tdm_state_remaining + (silence_period / 2)) &&
            len != 0 && trailer.window != 0 && trailer.command == 0)
        {
            packet_force_resend();
        }

        if (lbt_rssi != 0)
        {
            // reset the LBT listen time
            lbt_listen_time = 0;
            lbt_rand = 0;
        }

        if (len != 0 && trailer.window != 0)
        {
#error LED_ACTIVITY = LED_OFF;
        }

#ifdef INCLUDE_AES
        // If we have any packets that need decrypting lets do it now.
        if (tdm_state_remaining > tx_window_width / 2)
        {
            // If it is starting to get really full, we want to try decrypting
            // not just one, but a few packets.
            if (encrypt_buffer_getting_full())
            {
                while (!encrypt_buffer_getting_empty())
                {
                    decryptPackets();
                }
            }
            else
            {
                decryptPackets();
            }
        }
#endif // INCLUDE_AES

        // set right receive channel
        radio_set_channel(fhop_receive_channel());

        // re-enable the receiver
        radio_receiver_on();
    }
#endif // RADIO_SPLAT_TESTING_MODE
}

#if 0
/// build the timing table
static void 
tdm_build_timing_table(void)
{
   uint8_t j;
   uint16_t rate;
  bool golay_saved = feature_golay;
  feature_golay = false;
  
  for (rate=2; rate<256; rate=(rate*3)/2) {
     uint32_t latency_sum=0, per_byte_sum=0;
    uint8_t size = MAX_PACKET_LENGTH;
    radio_configure(rate);
    for (j=0; j<50; j++) {
       uint16_t time_0, time_max, t1, t2;
      radio_set_channel(1);
      radio_receiver_on();
      if (serial_read_available() > 0) {
        feature_golay = golay_saved;
        return;
      }
      t1 = timer2_tick();
      if (!radio_transmit(0, pbuf, 0xFFFF)) {
        break;
      }
      t2 = timer2_tick();
      radio_receiver_on();
      
      time_0 = t2-t1;
      
      radio_set_channel(2);
      t1 = timer2_tick();
      if (!radio_transmit(size, pbuf, 0xFFFF)) {
        if (size == 0) {
          break;
        }
        size /= 4;
        j--;
        continue;
      }
      
      t2 = timer2_tick();
      radio_receiver_on();
      
      time_max = t2-t1;
      latency_sum += time_0;
      per_byte_sum += ((size/2) + (time_max - time_0))/size;
    }
    if (j > 0) {
      printf("{ %u, %u, %u },\n",
            (unsigned)(radio_air_rate()),
            (unsigned)(latency_sum/j),
            (unsigned)(per_byte_sum/j));
    }
  }
  feature_golay = golay_saved;
}


// test hardware CRC code
static void 
crc_test(void)
{
   uint8_t d[4] = { 0x01, 0x00, 0xbb, 0xcc };
   uint16_t crc;
  uint16_t t1, t2;
  crc = crc16(4, &d[0]);
  printf("CRC: %x %x\n", crc, 0xb166);	
  t1 = timer2_tick();
  crc16(MAX_PACKET_LENGTH/2, pbuf);
  t2 = timer2_tick();
  printf("crc %u bytes took %u 16usec ticks\n",
        (unsigned)MAX_PACKET_LENGTH/2,
        t2-t1);
}

// test golay encoding
static void 
golay_test(void)
{
  uint8_t i;
  uint16_t t1, t2;
   uint8_t	buf[MAX_PACKET_LENGTH];
  for (i=0; i<MAX_PACKET_LENGTH/2; i++) {
    pbuf[i] = i;
  }
  t1 = timer2_tick();
  golay_encode(MAX_PACKET_LENGTH/2, pbuf, buf);
  t2 = timer2_tick();
  printf("encode %u bytes took %u 16usec ticks\n",
        (unsigned)MAX_PACKET_LENGTH/2,
        t2-t1);
  // add an error in the middle
  buf[MAX_PACKET_LENGTH/2] ^= 0x23;
  buf[1] ^= 0x70;
  t1 = timer2_tick();
  golay_decode(MAX_PACKET_LENGTH, buf, pbuf);
  t2 = timer2_tick();
  printf("decode %u bytes took %u 16usec ticks\n",
        (unsigned)MAX_PACKET_LENGTH,
        t2-t1);
  for (i=0; i<MAX_PACKET_LENGTH/2; i++) {
    if (pbuf[i] != i) {
      printf("golay error at %u\n", (unsigned)i);
    }
  }
}
#endif

// initialise the TDM subsystem
void tdm_init(void)
{
    uint16_t i;
    uint8_t air_rate = radio_air_rate();
    uint32_t window_width;

#define REGULATORY_MAX_WINDOW (((1000000UL / 16) * 4) / 10)
#define LBT_MIN_TIME_USEC     5000

    // tdm_build_timing_table();

    // calculate how many 16usec ticks it takes to send each byte
    ticks_per_byte = (8 + (8000000UL / (air_rate * 1000UL))) / 16;
    ticks_per_byte++;

    // calculate the minimum packet latency in 16 usec units
    // we initially assume a preamble length of 40 bits, then
    // adjust later based on actual preamble length. This is done
    // so that if one radio has antenna diversity and the other
    // doesn't, then they will both using the same TDM round timings
    packet_latency = (8 + (10 / 2)) * ticks_per_byte + 13;

    if (feature_golay)
    {
        max_data_packet_length = (MAX_PACKET_LENGTH / 2) - (6 + sizeof(trailer));

        // golay encoding doubles the cost per byte
        ticks_per_byte *= 2;

        // and adds 4 bytes
        packet_latency += 4 * ticks_per_byte;
    }
    else
    {
        max_data_packet_length = MAX_PACKET_LENGTH - sizeof(trailer);
    }

    // set the silence period to two times the packet latency
    silence_period = 2 * packet_latency;

    // set the transmit window to allow for 3 full sized packets
    window_width = 3 * (packet_latency + (max_data_packet_length * (uint32_t)ticks_per_byte));

    // min listen time is 5ms
    lbt_min_time = LBT_MIN_TIME_USEC / 16;

    // if LBT is enabled, we need at least 3*5ms of window width
    if (lbt_rssi != 0)
    {
        window_width = constrain(window_width, 3 * lbt_min_time, window_width);
    }

    // the window width cannot be more than 0.4 seconds to meet US
    // regulations
    if (window_width >= REGULATORY_MAX_WINDOW && num_fh_channels > 1)
    {
        window_width = REGULATORY_MAX_WINDOW;
    }

    // user specified window is in milliseconds
    if (window_width > param_get(PARAM_MAX_WINDOW) * (1000 / 16))
    {
        window_width = param_get(PARAM_MAX_WINDOW) * (1000 / 16);
    }

    // make sure it fits in the 13 bits of the trailer window
    if (window_width > 0x1fff)
    {
        window_width = 0x1fff;
    }

    tx_window_width = window_width;

    // now adjust the packet_latency for the actual preamble
    // length, so we get the right flight time estimates, while
    // not changing the round timings
    packet_latency += ((settings.preamble_length - 10) / 2) * ticks_per_byte;

    // tell the packet subsystem our max packet size, which it
    // needs to know for MAVLink packet boundary detection
    i = (tx_window_width - packet_latency) / ticks_per_byte;
    if (i > max_data_packet_length)
    {
        i = max_data_packet_length;
    }
    packet_set_max_xmit(i);

#ifdef TDM_SYNC_LOGIC
    TDM_SYNC_PIN = false;
#endif // TDM_SYNC_LOGIC

    // crc_test();

    // tdm_test_timing();

    // golay_test();
}

/// report tdm timings
///
void tdm_report_timing(void)
{
    printf("silence_period: %u\n", (unsigned)silence_period);
    delay_msec(1);
    printf("tx_window_width: %u\n", (unsigned)tx_window_width);
    delay_msec(1);
    printf("max_data_packet_length: %u\n", (unsigned)max_data_packet_length);
    delay_msec(1);
}
